Skip to content

Add Interactive Brokers Provider#1722

Merged
jjmata merged 8 commits into
we-promise:mainfrom
gian-reto:gta/ibkr-provider
May 12, 2026
Merged

Add Interactive Brokers Provider#1722
jjmata merged 8 commits into
we-promise:mainfrom
gian-reto:gta/ibkr-provider

Conversation

@gian-reto
Copy link
Copy Markdown
Contributor

@gian-reto gian-reto commented May 10, 2026

Unfortunately, my original PR (#1712) got closed, because GitHub rerouted my fork to point to maybe-finance/maybe when I pushed... No idea how that happened.

Anyway, I had to make a new PR. I addressed all the items pointed out by Codex and CodeRabbit, and the tests should now pass.

If it looks good to you, feel free to merge @jjmata!

Summary by CodeRabbit

  • New Features

    • Interactive Brokers (IBKR) integration: connect IBKR, import IBKR accounts, link/create accounts, sync & setup flows, and new IBKR settings/UI panels.
  • Improvements

    • Import/reconciliation of holdings, trades, dividends, cash transactions, and historical balances with better multi-currency and exchange-rate handling.
    • Monetary values and chart tooltips marked privacy-sensitive; transactions/trades prefer security logos.
  • Tests

    • Extensive test coverage for IBKR flows, imports, syncs, and related account operations.

Review Change Stack

@superagent-security superagent-security Bot added the contributor:verified Contributor passed trust analysis. label May 10, 2026
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 10, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Interactive Brokers (IBKR) provider: DB migrations, IbkrItem/IbkrAccount models, Provider::IbkrFlex client and Flex XML parser, processors to import holdings/trades/cash and upsert historical balances, account syncer hooks, controllers/routes/UI for setup/linking, exchange-rate and activity-security plumbing, tests/fixtures, and a Nix dev shell.

Changes

IBKR Provider Integration

Layer / File(s) Summary
Database / Migrations
db/migrate/*, db/schema.rb
Adds ibkr_items and ibkr_accounts, trades.extra JSONB, raw_equity_summary_payload on IBKR accounts, indexes, and FK constraints.
Core Models
app/models/ibkr_item.rb, app/models/ibkr_account.rb, app/models/family/ibkr_connectable.rb
New IBKR models and Family connectable concern with associations, validations, status enum, and helper methods.
Provider Adapter
app/models/provider/ibkr_adapter.rb, app/models/provider_connection_status.rb
Registers IBKR adapter, provides connection config lambdas, and integrates provider into connection status mapping.
API client
app/models/provider/ibkr_flex.rb
HTTParty-based IBKR Flex client implementing SendRequest + polling GetStatement with retry/backoff and error mapping.
Parser & Helpers
app/models/ibkr_item/report_parser.rb, app/models/ibkr_account/data_helpers.rb
Parses Flex XML into structured payloads and provides helpers to parse decimals/dates and resolve/create Security records.
Data Processors
app/models/ibkr_account/holdings_processor.rb, app/models/ibkr_account/activities_processor.rb, app/models/ibkr_account/historical_balances_sync.rb
Convert IBKR payloads to holdings, trades, cash transactions, and upsert historical balances.
Importer & Syncer
app/models/ibkr_item/importer.rb, app/models/ibkr_item/syncer.rb
Import workflow and per-item sync orchestration with health stats and account scheduling.
Account Processor / Integration
app/models/ibkr_account/processor.rb, app/models/account.rb, app/models/account/syncer.rb
Processor updates account balances, runs processors, repairs opening-anchor; Account.create_from_ibkr_account and syncer balance override hook added.
Provider Import Adapter
app/models/account/provider_import_adapter.rb
import_trade gains exchange_rate: argument and trade-attribute assignment updated.
Exchange rate & Activity-security
app/models/trade.rb, app/models/transaction.rb, app/models/transaction/activity_security_preloader.rb, app/models/balance/sync_cache.rb
Adds exchange_rate accessors+validation, activity-security preloader to avoid N+1s, Balance::SyncCache uses entryable.exchange_rate when present.
Controllers & Routes
app/controllers/ibkr_items_controller.rb, config/routes.rb, app/controllers/accounts_controller.rb, app/controllers/transactions_controller.rb
New IbkrItemsController (create/update/destroy/sync/setup/link flows); routes for account setup/link flows; Accounts and Transactions controllers preload activity securities and expose @ibkr_items.
Views / UI / JS
app/views/settings/providers/_ibkr_panel.html.erb, app/views/ibkr_items/*, app/views/holdings/*, app/views/trades/_trade.html.erb, app/views/transactions/_transaction.html.erb, app/components/UI/account/activity_date.html.erb, app/javascript/controllers/time_series_chart_controller.js
New IBKR settings panel and setup dialogs; UI changes mark monetary values as privacy-sensitive, prefer security logos for trades/transactions, and update chart tooltip styling.
Tests & Fixtures
test/models/*, test/controllers/*, test/fixtures/*, test/fixtures/files/ibkr/flex_statement.xml
Extensive tests added for parser/importer/processor/syncer/controller and exchange-rate behavior; fixtures include IBKR Flex XML and ibkr_items/accounts YAML.
Dev tooling
flake.nix
Adds a Nix flake dev shell for Ruby with Chromium/chromedriver, Postgres/Redis helpers, and environment setup.

Sequence Diagram

sequenceDiagram
  participant User
  participant App as Rails App
  participant IBKR as Provider::IbkrFlex
  participant DB as Database
  User->>App: Configure IBKR / trigger sync
  App->>IBKR: SendRequest -> poll GetStatement
  IBKR-->>App: FlexQueryResponse XML
  App->>App: ReportParser.parse → IbkrItem::Importer upsert accounts
  App->>App: Run HoldingsProcessor / ActivitiesProcessor / HistoricalBalancesSync
  App->>DB: Upsert holdings, trades, transactions, balances
  App->>User: UI shows linked accounts / setup completion
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

enhancement

Suggested reviewers

  • jjmata

"A rabbit hopped through code today,
nibbling bugs that hid away.
Parsers tidy, balances bloom,
syncs hum softly in the room.
🐇 Hooray — IBKR's come to play!"

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

@superagent-security superagent-security Bot added the pr:verified PR passed security analysis. label May 10, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a382d689c0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread config/routes.rb Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 19

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/models/holding/materializer.rb (1)

145-154: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Add test coverage for provider snapshot cleanup and reconsider broad deletion approach.

The logic assumes provider snapshots are complete (containing all securities for a date), but this assumption is not validated by tests. If a provider sends partial data (e.g., only AAPL holdings), the code will delete calculated GOOGL holdings for that date, causing data loss.

The safer approach already exists in cleanup_shadowed_calculated_holdings (lines 124-143), which uses a targeted subquery to delete only calculated holdings that have an exact match with a provider holding on the same (security_id, date, currency). Consider either:

  1. Add comprehensive test coverage that validates provider snapshot completeness and demonstrates the deletion is safe.
  2. Refactor to use targeted deletion like cleanup_shadowed_calculated_holdings, matching on (security_id, date, currency) instead of deleting all calculated holdings on the date.

The broad deletion at lines 149-151 is inconsistent with the safer, targeted approach used just above it and creates unnecessary risk of data loss for partial provider snapshots.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/models/holding/materializer.rb` around lines 145 - 154, The
cleanup_stale_calculated_rows_on_latest_provider_snapshot method currently
deletes all calculated holdings for a provider_snapshot_date, which risks
removing valid calculated rows when provider snapshots are partial; either add
tests that validate provider snapshot completeness for
account.latest_provider_holdings_snapshot_date and assert that deleting all
account.holdings.where(account_provider_id: nil, date: provider_snapshot_date)
is safe, or refactor this method to follow the targeted approach used in
cleanup_shadowed_calculated_holdings: construct a subquery/join between
account.holdings (where account_provider_id is nil) and provider holdings for
the same account and provider_snapshot_date and delete only calculated rows that
match on security_id, date and currency (and account/provider identifiers)
instead of deleting by date alone.
🧹 Nitpick comments (7)
app/models/holding.rb (1)

259-265: ⚡ Quick win

Consider logging conversion failures for observability.

The Money::ConversionError rescue silently falls back to the unconverted amount, which could produce incorrect weight calculations when exchange rates are missing. This silent failure makes it difficult to detect missing exchange rate data.

📊 Proposed enhancement to add debug logging
 def amount_in_account_currency
   return amount if currency == account.currency
 
   Money.new(amount, currency).exchange_to(account.currency, date: date).amount
 rescue Money::ConversionError
+  Rails.logger.debug(
+    "Holding#amount_in_account_currency - Missing exchange rate for #{currency}→#{account.currency} " \
+    "on #{date}, holding_id=#{id}"
+  )
   amount
 end
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/models/holding.rb` around lines 259 - 265, The rescue in
amount_in_account_currency currently swallows Money::ConversionError; update it
to log the conversion failure (including source currency, target
account.currency, date, and the error message/exception) before returning amount
so missing exchange-rate incidents are observable; reference the
amount_in_account_currency method and the Money::ConversionError rescue block
and use the model logger (e.g., Rails.logger or logger) to emit a warn or
error-level message with enough context to trace the failed conversion.
app/models/ibkr_item/unlinking.rb (1)

24-24: ⚡ Quick win

Consider batch deletion for better performance.

Line 24 destroys links individually in a loop, which can be slow when many links exist. If AccountProvider callbacks aren't required during unlink, consider using destroy_all for better performance.

⚡ Proposed optimization
 ActiveRecord::Base.transaction do
   Holding.where(account_provider_id: link_ids).update_all(account_provider_id: nil) if link_ids.any?
-  links.each(&:destroy!)
+  AccountProvider.where(id: link_ids).destroy_all
 end

Note: Only apply this if AccountProvider#destroy callbacks aren't needed during unlink. If callbacks are required (e.g., for cleanup), keep the current implementation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/models/ibkr_item/unlinking.rb` at line 24, The current unlink loop uses
links.each(&:destroy!) which deletes each Link record individually and can be
slow; if AccountProvider#destroy callbacks are not required during unlink,
replace the per-record destroy with a batched deletion (e.g., use destroy_all or
delete_all on the links relation) to improve performance; locate the call to
links.each(&:destroy!) in unlinking.rb (the unlink flow that pertains to
AccountProvider) and switch to links.destroy_all (or links.delete_all if
callbacks and validations must be skipped) ensuring you pick destroy_all only if
callbacks are safe to omit and AccountProvider#destroy is not needed.
test/models/snaptrade_account_processor_test.rb (1)

134-134: ⚡ Quick win

Avoid stubbing set_current_balance in this behavior test.

Line 134 couples this test to internals while the test already asserts output state (balance, cash_balance, currency). Dropping the stub will keep the test focused on behavior and less brittle.

Suggested change
-    Account.any_instance.stubs(:set_current_balance)

As per coding guidelines: “Distinguish between commands and query methods. Test output of query methods … Never test the implementation details of one class in another class's test suite.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@test/models/snaptrade_account_processor_test.rb` at line 134, Remove the
fragile stub of Account.any_instance.stubs(:set_current_balance) so the test
exercises the real Account#set_current_balance behavior and only asserts
observable outputs (balance, cash_balance, currency); delete that stub line and,
if the real method requires setup, initialize the Account fixture or its
dependencies (e.g., balances, transactions) instead or, if necessary, stub a
lower-level external dependency used by Account#set_current_balance rather than
stubbing the method under test.
app/views/ibkr_items/select_existing_account.html.erb (1)

24-24: ⚡ Quick win

Move currency formatting to the server side.

The view constructs a Money::Currency object and formats the balance inline. Per coding guidelines, currencies should be formatted server-side and passed to views for display only. This logic should move to the controller or a helper method.

🔄 Suggested refactor

In the controller, preformat the balance:

`@available_ibkr_accounts` = `@available_ibkr_accounts.map` do |acct|
  OpenStruct.new(
    id: acct.id,
    name: acct.name,
    ibkr_account_id: acct.ibkr_account_id,
    currency: acct.currency,
    formatted_balance: number_to_currency(
      acct.current_balance || 0,
      unit: Money::Currency.new(acct.currency || "USD").symbol
    )
  )
end

Then in the view:

-<%= ibkr_account.currency %> • Balance: <%= number_to_currency((ibkr_account.current_balance || 0), unit: Money::Currency.new(ibkr_account.currency || "USD").symbol) %>
+<%= ibkr_account.currency %> • Balance: <%= ibkr_account.formatted_balance %>

As per coding guidelines: "Format currencies, numbers, dates, and other values server-side, then pass to Stimulus controllers for display only".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/ibkr_items/select_existing_account.html.erb` at line 24, The view
is formatting currency inline (using number_to_currency and Money::Currency.new
on ibkr_account.current_balance); move this logic to the server by adding a
formatted_balance attribute for each account in the controller (e.g., when
preparing `@available_ibkr_accounts`) or by creating a helper method (e.g.,
format_currency_balance(account) used from the controller) that computes
number_to_currency(acct.current_balance || 0, unit:
Money::Currency.new(acct.currency || "USD").symbol) and pass that
formatted_balance to the view so the template only renders
ibkr_account.formatted_balance.
app/views/settings/providers/_ibkr_panel.html.erb (1)

11-53: ⚡ Quick win

Fix invalid nested list markup in setup instructions.

<ul> elements are currently direct children of <ol>. They should be nested inside the parent <li> to keep semantic structure valid.

Suggested structure adjustment
-    <ol>
-      <li>...enable the following sections:</li>
-      <ul class="list-disc list-inside mb-2">
-        <li>Account Information: ...</li>
-        ...
-      </ul>
+    <ol>
+      <li>
+        ...enable the following sections:
+        <ul class="list-disc list-inside mb-2">
+          <li>Account Information: ...</li>
+          ...
+        </ul>
+      </li>

As per coding guidelines: {app/views/**,app/helpers/**}: “Always generate semantic HTML.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/settings/providers/_ibkr_panel.html.erb` around lines 11 - 53, The
ordered list (<ol>) in _ibkr_panel.html.erb has multiple <ul> elements as direct
children which breaks semantic nesting; move each <ul> so it is nested inside
the preceding related <li> (e.g., wrap the "Cash Report", "Cash Transactions",
"Net Asset Value", "Open Positions", "Trades" and the "Set the following
configuration options" lists inside their parent <li> elements) so every <ul> is
a child of its corresponding <li> and the <ol> contains only <li> children.
app/views/ibkr_items/_ibkr_item.html.erb (1)

11-13: ⚡ Quick win

Replace raw hex color classes with design-system tokens.

The IB badge uses bg-[#D32F2F]/10 and text-[#D32F2F], which breaks token consistency.

Suggested token-based tweak
-        <div class="flex items-center justify-center h-8 w-8 rounded-full bg-[`#D32F2F`]/10">
-          <span class="text-[`#D32F2F`] text-xs font-medium">IB</span>
+        <div class="flex items-center justify-center h-8 w-8 rounded-full bg-destructive/10">
+          <span class="text-destructive text-xs font-medium">IB</span>
         </div>

As per coding guidelines: app/**/*.{erb,css,html}: “Use Tailwind CSS design tokens… instead of raw Tailwind classes.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/views/ibkr_items/_ibkr_item.html.erb` around lines 11 - 13, Replace the
raw hex Tailwind classes on the IB badge with design-system token classes: swap
the div's bg-[`#D32F2F`]/10 for the appropriate background token (e.g.,
bg-destructive-10 or the project's equivalent token) and replace the span's
text-[`#D32F2F`] with the matching text token (e.g., text-destructive-600 or the
project's equivalent); update the element with those token class names (the div
with the rounded bg and the span with "IB") so the component uses the shared
Tailwind design tokens instead of hard-coded hex values.
app/models/provider/ibkr_flex.rb (1)

19-21: ⚡ Quick win

Externalize the Flex endpoint and timeout instead of hard-coding them.

These are provider configuration values, so keeping them env-backed will make sandboxing, testing, and operational overrides easier without another code change.

Proposed refactor
-  base_uri "https://ndcdyn.interactivebrokers.com/AccountManagement/FlexWebService"
-  headers "User-Agent" => "Sure Finance IBKR Flex Client"
-  default_options.merge!({ timeout: 120 }.merge(httparty_ssl_options))
+  base_uri ENV.fetch("IBKR_FLEX_BASE_URL", "https://ndcdyn.interactivebrokers.com/AccountManagement/FlexWebService")
+  headers "User-Agent" => ENV.fetch("IBKR_FLEX_USER_AGENT", "Sure Finance IBKR Flex Client")
+  default_options.merge!({
+    timeout: ENV.fetch("IBKR_FLEX_TIMEOUT", 120).to_i
+  }.merge(httparty_ssl_options))

As per coding guidelines: use environment variables instead of hard-coded values in configuration.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/models/provider/ibkr_flex.rb` around lines 19 - 21, Replace hard-coded
Flex endpoint and timeout with env-backed config: read endpoint from an
environment variable (e.g. ENV['IBKR_FLEX_ENDPOINT'] with a sensible default
like the current URL) and read timeout from an environment variable (e.g.
ENV['IBKR_FLEX_TIMEOUT'] parsed to integer with default 120). Use those
variables when calling base_uri and when merging into default_options (the
existing default_options.merge! call) while keeping the existing headers and
httparty_ssl_options. Update any relevant initialization or docs to note the new
ENV keys.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/controllers/ibkr_items_controller.rb`:
- Around line 48-49: The redirect calls using redirect_to
settings_providers_path with status: :unprocessable_entity should use a 3xx
redirect status instead of 422; change the status option to a redirect status
(e.g. :see_other or :found) on the redirect_to calls (the ones referencing
settings_providers_path and `@error_message`) so both occurrences that currently
pass status: :unprocessable_entity use a 3xx symbol (prefer :see_other for
POST/Turbo compatibility).
- Around line 185-188: The rescue block in ibkr_items_controller.rb currently
logs the full exception but passes e.message to the end-user via redirect_to
settings_providers_path, alert: t(".failed", message: e.message), which can leak
internals; keep the Rails.logger.error(...) as-is (or enhance it with
e.full_message/backtrace) but change the redirect to use a generic translated
message (e.g. redirect_to settings_providers_path, alert: t(".failed") or
t(".failed_generic")) and remove the message: e.message interpolation; also
update the corresponding translation key (".failed") so it does not expect a
message param.

In `@app/models/account/provider_import_adapter.rb`:
- Around line 594-595: The export currently always assigns exchange_rate in the
attributes hash (exchange_rate) which causes persisted FX rates to be cleared
when the provider omits the field; change the builder in
provider_import_adapter.rb so you only add the exchange_rate attribute when the
incoming value is present (e.g., skip or merge the key when exchange_rate is
nil/blank) instead of unconditionally setting exchange_rate alongside
investment_activity_label, ensuring re-imports don't overwrite an existing
persisted rate.

In `@app/models/family/ibkr_connectable.rb`:
- Around line 12-15: In create_ibkr_item! ensure blank strings don't bypass the
default: replace the current item_name || "Interactive Brokers" usage in the
ibkr_items.create! call with a presence-aware check (e.g. use item_name.presence
or equivalent) so that "" will fall back to "Interactive Brokers" when building
the record in create_ibkr_item!.

In `@app/models/ibkr_account/activities_processor.rb`:
- Around line 150-163: The supported_trade? predicate is rejecting
commission-free trades by requiring row[:ib_commission] and
row[:ib_commission_currency]; remove those two presence checks from
supported_trade? so trades without commission values still pass validation
(leave fx_rate_available?(row) and all other required checks intact), since
import_commission_transaction already handles nil/zero commission values. Ensure
no other logic relies on those fields being present at this validation point.

In `@app/models/ibkr_account/data_helpers.rb`:
- Around line 58-60: The trade_date_for method currently falls back silently to
Date.current when parse_date(row.with_indifferent_access[:trade_date]) returns
nil; update trade_date_for to detect when parse_date returns nil and emit a
structured log (e.g., Rails.logger.warn or the model's logger) including the
original trade_date string and identifying row/context (such as account id,
symbol or full row hash) before returning Date.current so parsing failures are
observable; keep the existing return behavior but add the log in the branch
where parse_date is nil and reference the trade_date_for and parse_date methods
and supported_trade? validation flow.

In `@app/models/ibkr_account/historical_balances_sync.rb`:
- Around line 29-31: The comparison normalizes only the incoming row via the
local variable `currency` but not the stored value; change the guard to compare
normalized values (e.g., compare `currency` to `ibkr_account.currency` after
normalizing the latter) so both sides use the same case/format and you don't
drop valid rows — use `ibkr_account.currency.to_s.upcase` (or `&.upcase`) when
comparing to `currency`.

In `@app/models/ibkr_account/holdings_processor.rb`:
- Around line 40-50: process_group currently takes cost_basis from rows.first
which yields incorrect cost for merged lots; instead compute a weighted-average
per-share cost across rows by summing each row's
(parse_decimal(row[:cost_basis_price]) * parse_decimal(row[:position])) and
dividing by total quantity (rows.sum of parse_decimal(row[:position]) with
BigDecimal("0") fallback), skipping rows with missing values as appropriate,
then use that per-share cost_basis when creating the Holding; update references
in process_group to use this computed weighted per-share cost (and ensure
Holding stores cost_basis as per-share value).

In `@app/models/ibkr_account/processor.rb`:
- Around line 11-15: The call to repair_default_opening_anchor! runs too early
and must be moved after the activity sync so non-valuation entries exist; update
the sequence in the processor to call update_account_balance!, run
IbkrAccount::HoldingsProcessor.new(ibkr_account).process and
IbkrAccount::ActivitiesProcessor.new(ibkr_account).process first, then invoke
repair_default_opening_anchor! so the method (repair_default_opening_anchor!)
can detect the created non-valuation entries produced by
IbkrAccount::ActivitiesProcessor and perform the first-sync correction
correctly.

In `@app/models/ibkr_item.rb`:
- Line 22: The scope `syncable` is filtering `token` with a value-based check
(`where.not(token: [ nil, "" ])`) which fails for non-deterministically
encrypted `token`; change the filter to avoid comparing plaintext values — e.g.
replace the token predicate with a NULL-only check (`where.not(token: nil)`) or
maintain a separate boolean/metadata column for token presence, while leaving
the `query_id` check as-is; update the `scope :syncable` definition accordingly
(reference: scope :syncable, token, query_id).

In `@app/models/ibkr_item/syncer.rb`:
- Around line 12-16: The code currently raises a plain StandardError after
calling collect_health_stats which causes the generic rescue to double-report
the same issue as a sync_error; change the behavior so missing credentials are
signaled with a specific exception (e.g., define IBKRCredentialsMissingError or
IBKR::CredentialsMissing) or simply return early instead of raising
StandardError in the credential-check branches (the block around
ibkr_item.credentials_configured? and the similar block at lines ~52-58); also
add an explicit rescue for that new credentials exception (or rely on the early
return) so the existing generic rescue does not record a duplicate sync_error
for auth problems.

In `@app/models/trade.rb`:
- Around line 21-34: The exchange_rate= setter currently accepts non-positive
numbers; update it so after converting value to Float in exchange_rate= (method)
you check that normalized_value > 0 and treat non-positive values as invalid: if
normalized_value is not > 0 raise an ArgumentError (or otherwise jump to the
rescue path) so the subsequent self.extra merge stores either a positive numeric
exchange_rate with "exchange_rate_invalid" => false or stores the original value
with "exchange_rate_invalid" => true; reference the exchange_rate= method,
normalized_value, and the extra / "exchange_rate_invalid" merge when making the
change.

In `@app/models/transaction.rb`:
- Around line 159-168: activity_security currently memoizes `@activity_security`
and can return stale objects if activity_security_id (derived from
extra["security_id"]) changes; modify activity_security to track the id used for
the cache (e.g. store a cached_id alongside `@activity_security`) and only return
the memoized object if cached_id == activity_security_id, otherwise reload with
Security.find_by and update cached_id; also update
set_preloaded_activity_security(security) to set both `@activity_security` and the
cached_id to security&.id so preloaded values stay in sync.

In `@app/views/ibkr_items/_ibkr_item.html.erb`:
- Around line 19-107: Replace all hard-coded user-facing strings in this partial
with calls to the t(...) i18n helper and move keys into a hierarchical namespace
(e.g., activerecord/views.ibkr_items or views.ibkr_items) so they can be
localized; specifically swap the visible texts "Deletion in progress", "Flex Web
Service", "Syncing", "Credentials need attention", "Error", "Synced ... ago.
...", "Never synced.", menu/link labels "Set up accounts", "Delete", empty-state
headings "Accounts need setup", "No IBKR accounts discovered yet.", and
explanatory lines into translation lookups, and pass translated strings into
components/constructors used here (DS::Tooltip, DS::Menu items, DS::Link, and
CustomConfirm.for_resource_deletion) and into ProviderSyncSummary render calls
as needed so all displayed copy uses t(...) keys.

In `@app/views/ibkr_items/setup_accounts.html.erb`:
- Line 146: Replace the slash-opacity token classes in the markup: change
"border-success/20" to the supported equivalents (e.g. "border-success
border-opacity-20") and change "bg-success/5" to "bg-success bg-opacity-5" (or
other non-slash token variants your design system supports) in the element that
currently contains those classes so the utility classes generate CSS correctly.

In `@app/views/settings/providers/_ibkr_panel.html.erb`:
- Around line 10-120: The partial contains many hard-coded user-facing strings
(setup steps, list items, headings, placeholders, button labels like "Sync" and
"Update Configuration", status text from ibkr_item.sync_status_summary, and the
"Disconnect Interactive Brokers?" confirm) that must be replaced with i18n
lookups; update this view to call t(...) for each visible string (e.g.,
headings, each li text, form.label/placeholder values for :query_id and :token,
button labels used in button_to and form.submit, the turbo_confirm value, the
"Not configured." and the explanatory paragraph) and then add corresponding keys
under config/locales/en.yml (namespace them under something like ibkr_panel.* to
keep them discoverable). Ensure dynamic text like ibkr_item.sync_status_summary
remains unchanged but any surrounding/static fragments use t() and include
interpolation keys where needed.

In `@app/views/settings/providers/show.html.erb`:
- Line 82: Replace the hard-coded section title string with the i18n helper and
add the corresponding locale key; in the ERB view change the settings_section
call to use t('settings.providers.ibkr.title') instead of the literal
"Interactive Brokers Flex Query (beta)" and add a new entry under en -> settings
-> providers -> ibkr -> title in config/locales/en.yml with the same text.

In `@db/migrate/20260510140000_create_ibkr_items_and_accounts.rb`:
- Around line 6-9: The migration creates defaulted columns that remain
nullable—change the column definitions for status, scheduled_for_deletion,
pending_account_setup, and raw_payload to include null: false so defaults cannot
be overridden with NULL, and apply the same null: false enforcement to the other
defaulted state/payload columns referenced around lines 26–29 in this migration;
update their t.string/t.boolean/t.jsonb declarations to add null: false to
ensure DB-level non-null guarantees.

In `@flake.nix`:
- Around line 120-130: The dev Redis instance is using the default TCP endpoint
and may collide with a system Redis; update redis_start and redis_stop to use a
repo-local Unix socket under REDIS_DIR (e.g., "$REDIS_DIR/redis.sock") so the
instance is isolated. In redis_start (function redis_start) add the redis-server
option to create the Unix socket (--unixsocket "$REDIS_DIR/redis.sock") and keep
pidfile/log/dir as before; in redis_stop (function redis_stop) invoke redis-cli
with the socket option (e.g., redis-cli -s "$REDIS_DIR/redis.sock" shutdown
nosave ...) so it only shuts down the repo-local server, and ensure any
references to host/port are removed or replaced with the socket path.

---

Outside diff comments:
In `@app/models/holding/materializer.rb`:
- Around line 145-154: The
cleanup_stale_calculated_rows_on_latest_provider_snapshot method currently
deletes all calculated holdings for a provider_snapshot_date, which risks
removing valid calculated rows when provider snapshots are partial; either add
tests that validate provider snapshot completeness for
account.latest_provider_holdings_snapshot_date and assert that deleting all
account.holdings.where(account_provider_id: nil, date: provider_snapshot_date)
is safe, or refactor this method to follow the targeted approach used in
cleanup_shadowed_calculated_holdings: construct a subquery/join between
account.holdings (where account_provider_id is nil) and provider holdings for
the same account and provider_snapshot_date and delete only calculated rows that
match on security_id, date and currency (and account/provider identifiers)
instead of deleting by date alone.

---

Nitpick comments:
In `@app/models/holding.rb`:
- Around line 259-265: The rescue in amount_in_account_currency currently
swallows Money::ConversionError; update it to log the conversion failure
(including source currency, target account.currency, date, and the error
message/exception) before returning amount so missing exchange-rate incidents
are observable; reference the amount_in_account_currency method and the
Money::ConversionError rescue block and use the model logger (e.g., Rails.logger
or logger) to emit a warn or error-level message with enough context to trace
the failed conversion.

In `@app/models/ibkr_item/unlinking.rb`:
- Line 24: The current unlink loop uses links.each(&:destroy!) which deletes
each Link record individually and can be slow; if AccountProvider#destroy
callbacks are not required during unlink, replace the per-record destroy with a
batched deletion (e.g., use destroy_all or delete_all on the links relation) to
improve performance; locate the call to links.each(&:destroy!) in unlinking.rb
(the unlink flow that pertains to AccountProvider) and switch to
links.destroy_all (or links.delete_all if callbacks and validations must be
skipped) ensuring you pick destroy_all only if callbacks are safe to omit and
AccountProvider#destroy is not needed.

In `@app/models/provider/ibkr_flex.rb`:
- Around line 19-21: Replace hard-coded Flex endpoint and timeout with
env-backed config: read endpoint from an environment variable (e.g.
ENV['IBKR_FLEX_ENDPOINT'] with a sensible default like the current URL) and read
timeout from an environment variable (e.g. ENV['IBKR_FLEX_TIMEOUT'] parsed to
integer with default 120). Use those variables when calling base_uri and when
merging into default_options (the existing default_options.merge! call) while
keeping the existing headers and httparty_ssl_options. Update any relevant
initialization or docs to note the new ENV keys.

In `@app/views/ibkr_items/_ibkr_item.html.erb`:
- Around line 11-13: Replace the raw hex Tailwind classes on the IB badge with
design-system token classes: swap the div's bg-[`#D32F2F`]/10 for the appropriate
background token (e.g., bg-destructive-10 or the project's equivalent token) and
replace the span's text-[`#D32F2F`] with the matching text token (e.g.,
text-destructive-600 or the project's equivalent); update the element with those
token class names (the div with the rounded bg and the span with "IB") so the
component uses the shared Tailwind design tokens instead of hard-coded hex
values.

In `@app/views/ibkr_items/select_existing_account.html.erb`:
- Line 24: The view is formatting currency inline (using number_to_currency and
Money::Currency.new on ibkr_account.current_balance); move this logic to the
server by adding a formatted_balance attribute for each account in the
controller (e.g., when preparing `@available_ibkr_accounts`) or by creating a
helper method (e.g., format_currency_balance(account) used from the controller)
that computes number_to_currency(acct.current_balance || 0, unit:
Money::Currency.new(acct.currency || "USD").symbol) and pass that
formatted_balance to the view so the template only renders
ibkr_account.formatted_balance.

In `@app/views/settings/providers/_ibkr_panel.html.erb`:
- Around line 11-53: The ordered list (<ol>) in _ibkr_panel.html.erb has
multiple <ul> elements as direct children which breaks semantic nesting; move
each <ul> so it is nested inside the preceding related <li> (e.g., wrap the
"Cash Report", "Cash Transactions", "Net Asset Value", "Open Positions",
"Trades" and the "Set the following configuration options" lists inside their
parent <li> elements) so every <ul> is a child of its corresponding <li> and the
<ol> contains only <li> children.

In `@test/models/snaptrade_account_processor_test.rb`:
- Line 134: Remove the fragile stub of
Account.any_instance.stubs(:set_current_balance) so the test exercises the real
Account#set_current_balance behavior and only asserts observable outputs
(balance, cash_balance, currency); delete that stub line and, if the real method
requires setup, initialize the Account fixture or its dependencies (e.g.,
balances, transactions) instead or, if necessary, stub a lower-level external
dependency used by Account#set_current_balance rather than stubbing the method
under test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: dc1fac98-33d2-4b3e-ab9c-14eacdb199b8

📥 Commits

Reviewing files that changed from the base of the PR and between c92b984 and a382d68.

⛔ Files ignored due to path filters (1)
  • flake.lock is excluded by !**/*.lock
📒 Files selected for processing (72)
  • app/components/UI/account/activity_date.html.erb
  • app/controllers/accounts_controller.rb
  • app/controllers/ibkr_items_controller.rb
  • app/controllers/transactions_controller.rb
  • app/javascript/controllers/time_series_chart_controller.js
  • app/models/account.rb
  • app/models/account/provider_import_adapter.rb
  • app/models/account/syncer.rb
  • app/models/balance/sync_cache.rb
  • app/models/data_enrichment.rb
  • app/models/family.rb
  • app/models/family/ibkr_connectable.rb
  • app/models/holding.rb
  • app/models/holding/materializer.rb
  • app/models/holding/portfolio_cache.rb
  • app/models/ibkr_account.rb
  • app/models/ibkr_account/activities_processor.rb
  • app/models/ibkr_account/data_helpers.rb
  • app/models/ibkr_account/historical_balances_sync.rb
  • app/models/ibkr_account/holdings_processor.rb
  • app/models/ibkr_account/processor.rb
  • app/models/ibkr_item.rb
  • app/models/ibkr_item/importer.rb
  • app/models/ibkr_item/provided.rb
  • app/models/ibkr_item/report_parser.rb
  • app/models/ibkr_item/syncer.rb
  • app/models/ibkr_item/unlinking.rb
  • app/models/provider/ibkr_adapter.rb
  • app/models/provider/ibkr_flex.rb
  • app/models/provider_connection_status.rb
  • app/models/snaptrade_account/processor.rb
  • app/models/trade.rb
  • app/models/transaction.rb
  • app/models/transaction/activity_security_preloader.rb
  • app/views/accounts/index.html.erb
  • app/views/holdings/_cash.html.erb
  • app/views/holdings/_cost_basis_cell.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/ibkr_items/_ibkr_item.html.erb
  • app/views/ibkr_items/select_existing_account.html.erb
  • app/views/ibkr_items/setup_accounts.html.erb
  • app/views/settings/providers/_ibkr_panel.html.erb
  • app/views/settings/providers/show.html.erb
  • app/views/trades/_trade.html.erb
  • app/views/transactions/_transaction.html.erb
  • config/locales/views/ibkr_items/en.yml
  • config/routes.rb
  • db/migrate/20260510140000_create_ibkr_items_and_accounts.rb
  • db/migrate/20260510150000_add_extra_to_trades.rb
  • db/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rb
  • db/schema.rb
  • flake.nix
  • test/controllers/ibkr_items_controller_test.rb
  • test/fixtures/files/ibkr/flex_statement.xml
  • test/fixtures/ibkr_accounts.yml
  • test/fixtures/ibkr_items.yml
  • test/models/account/provider_import_adapter_test.rb
  • test/models/account/syncer_test.rb
  • test/models/account_ibkr_creation_test.rb
  • test/models/account_test.rb
  • test/models/balance/sync_cache_test.rb
  • test/models/holding/materializer_test.rb
  • test/models/holding/portfolio_cache_test.rb
  • test/models/holding_test.rb
  • test/models/ibkr_account/historical_balances_sync_test.rb
  • test/models/ibkr_account_processor_test.rb
  • test/models/ibkr_item_importer_test.rb
  • test/models/ibkr_item_report_parser_test.rb
  • test/models/snaptrade_account_processor_test.rb
  • test/models/trade_test.rb
  • test/models/transaction/activity_security_preloader_test.rb
  • test/models/transaction_test.rb

Comment thread app/controllers/ibkr_items_controller.rb Outdated
Comment thread app/controllers/ibkr_items_controller.rb
Comment thread app/models/account/provider_import_adapter.rb Outdated
Comment thread app/models/family/ibkr_connectable.rb
Comment thread app/models/ibkr_account/activities_processor.rb
Comment thread app/views/ibkr_items/setup_accounts.html.erb
Comment thread app/views/settings/providers/_ibkr_panel.html.erb Outdated
Comment thread app/views/settings/providers/show.html.erb Outdated
Comment thread db/migrate/20260510140000_create_ibkr_items_and_accounts.rb Outdated
Comment thread flake.nix Outdated
@gian-reto
Copy link
Copy Markdown
Contributor Author

I'll address the additional comments tomorrow!

@gian-reto gian-reto force-pushed the gta/ibkr-provider branch from 5b37aeb to 7732084 Compare May 10, 2026 00:30
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
app/models/ibkr_account/processor.rb (1)

12-14: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Move opening-anchor repair after activities import

On Line 12, repair_default_opening_anchor! runs before IbkrAccount::ActivitiesProcessor. But the repair guard on Line 44 requires existing non-valuation entries, so first sync can skip the repair path.

Suggested minimal reorder
   def process
     return unless ibkr_account.current_account.present?

     update_account_balance!
-    repair_default_opening_anchor!
     IbkrAccount::HoldingsProcessor.new(ibkr_account).process
     IbkrAccount::ActivitiesProcessor.new(ibkr_account).process
+    repair_default_opening_anchor!

     ibkr_account.current_account.broadcast_sync_complete
   end

Also applies to: 44-44

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/models/ibkr_account/processor.rb` around lines 12 - 14, The call to
repair_default_opening_anchor! currently runs before
IbkrAccount::ActivitiesProcessor and can be skipped due to the repair guard that
requires non-valuation entries; move the repair_default_opening_anchor!
invocation to run after
IbkrAccount::ActivitiesProcessor.new(ibkr_account).process (i.e., reorder so
HoldingsProcessor.process, ActivitiesProcessor.process, then
repair_default_opening_anchor!) to ensure the guard sees imported activity
entries.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@app/models/ibkr_account/processor.rb`:
- Around line 12-14: The call to repair_default_opening_anchor! currently runs
before IbkrAccount::ActivitiesProcessor and can be skipped due to the repair
guard that requires non-valuation entries; move the
repair_default_opening_anchor! invocation to run after
IbkrAccount::ActivitiesProcessor.new(ibkr_account).process (i.e., reorder so
HoldingsProcessor.process, ActivitiesProcessor.process, then
repair_default_opening_anchor!) to ensure the guard sees imported activity
entries.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f17d2b03-e321-478b-afd9-24a17420967e

📥 Commits

Reviewing files that changed from the base of the PR and between 5b37aeb and 7732084.

📒 Files selected for processing (64)
  • app/components/UI/account/activity_date.html.erb
  • app/controllers/accounts_controller.rb
  • app/controllers/ibkr_items_controller.rb
  • app/controllers/transactions_controller.rb
  • app/javascript/controllers/time_series_chart_controller.js
  • app/models/account.rb
  • app/models/account/provider_import_adapter.rb
  • app/models/account/syncer.rb
  • app/models/balance/sync_cache.rb
  • app/models/data_enrichment.rb
  • app/models/family.rb
  • app/models/family/ibkr_connectable.rb
  • app/models/holding/portfolio_cache.rb
  • app/models/ibkr_account.rb
  • app/models/ibkr_account/activities_processor.rb
  • app/models/ibkr_account/data_helpers.rb
  • app/models/ibkr_account/historical_balances_sync.rb
  • app/models/ibkr_account/holdings_processor.rb
  • app/models/ibkr_account/processor.rb
  • app/models/ibkr_item.rb
  • app/models/ibkr_item/importer.rb
  • app/models/ibkr_item/provided.rb
  • app/models/ibkr_item/report_parser.rb
  • app/models/ibkr_item/syncer.rb
  • app/models/ibkr_item/unlinking.rb
  • app/models/provider/ibkr_adapter.rb
  • app/models/provider/ibkr_flex.rb
  • app/models/provider_connection_status.rb
  • app/models/trade.rb
  • app/models/transaction.rb
  • app/models/transaction/activity_security_preloader.rb
  • app/views/accounts/index.html.erb
  • app/views/holdings/_cash.html.erb
  • app/views/holdings/_cost_basis_cell.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/ibkr_items/_ibkr_item.html.erb
  • app/views/ibkr_items/select_existing_account.html.erb
  • app/views/ibkr_items/setup_accounts.html.erb
  • app/views/settings/providers/_ibkr_panel.html.erb
  • app/views/settings/providers/show.html.erb
  • app/views/trades/_trade.html.erb
  • app/views/transactions/_transaction.html.erb
  • config/locales/views/ibkr_items/en.yml
  • config/routes.rb
  • db/migrate/20260510140000_create_ibkr_items_and_accounts.rb
  • db/migrate/20260510150000_add_extra_to_trades.rb
  • db/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rb
  • db/schema.rb
  • test/controllers/ibkr_items_controller_test.rb
  • test/fixtures/files/ibkr/flex_statement.xml
  • test/fixtures/ibkr_accounts.yml
  • test/fixtures/ibkr_items.yml
  • test/models/account/provider_import_adapter_test.rb
  • test/models/account/syncer_test.rb
  • test/models/account_ibkr_creation_test.rb
  • test/models/balance/sync_cache_test.rb
  • test/models/holding/portfolio_cache_test.rb
  • test/models/ibkr_account/historical_balances_sync_test.rb
  • test/models/ibkr_account_processor_test.rb
  • test/models/ibkr_item_importer_test.rb
  • test/models/ibkr_item_report_parser_test.rb
  • test/models/trade_test.rb
  • test/models/transaction/activity_security_preloader_test.rb
  • test/models/transaction_test.rb
✅ Files skipped from review due to trivial changes (12)
  • app/views/holdings/_cash.html.erb
  • app/views/holdings/_cost_basis_cell.html.erb
  • test/fixtures/ibkr_items.yml
  • app/javascript/controllers/time_series_chart_controller.js
  • app/components/UI/account/activity_date.html.erb
  • test/fixtures/files/ibkr/flex_statement.xml
  • app/views/holdings/_holding.html.erb
  • test/fixtures/ibkr_accounts.yml
  • test/models/ibkr_item_report_parser_test.rb
  • test/controllers/ibkr_items_controller_test.rb
  • config/locales/views/ibkr_items/en.yml
  • app/models/family/ibkr_connectable.rb
🚧 Files skipped from review as they are similar to previous changes (50)
  • app/models/family.rb
  • app/controllers/transactions_controller.rb
  • app/models/ibkr_item/importer.rb
  • app/models/data_enrichment.rb
  • test/models/transaction/activity_security_preloader_test.rb
  • app/models/balance/sync_cache.rb
  • app/models/ibkr_item/provided.rb
  • test/models/account/syncer_test.rb
  • app/models/ibkr_item/unlinking.rb
  • app/views/accounts/index.html.erb
  • app/models/provider_connection_status.rb
  • db/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rb
  • app/models/trade.rb
  • app/views/ibkr_items/_ibkr_item.html.erb
  • test/models/ibkr_account/historical_balances_sync_test.rb
  • app/views/trades/_trade.html.erb
  • app/views/settings/providers/show.html.erb
  • config/routes.rb
  • test/models/ibkr_item_importer_test.rb
  • app/models/transaction.rb
  • app/models/account.rb
  • test/models/ibkr_account_processor_test.rb
  • app/views/ibkr_items/select_existing_account.html.erb
  • test/models/account/provider_import_adapter_test.rb
  • test/models/account_ibkr_creation_test.rb
  • app/views/transactions/_transaction.html.erb
  • app/models/provider/ibkr_adapter.rb
  • app/models/holding/portfolio_cache.rb
  • db/migrate/20260510150000_add_extra_to_trades.rb
  • app/models/ibkr_account.rb
  • test/models/balance/sync_cache_test.rb
  • app/models/ibkr_account/data_helpers.rb
  • app/controllers/accounts_controller.rb
  • app/models/transaction/activity_security_preloader.rb
  • app/models/ibkr_account/holdings_processor.rb
  • test/models/holding/portfolio_cache_test.rb
  • test/models/trade_test.rb
  • app/models/ibkr_account/historical_balances_sync.rb
  • app/views/settings/providers/_ibkr_panel.html.erb
  • app/views/ibkr_items/setup_accounts.html.erb
  • test/models/transaction_test.rb
  • app/models/ibkr_item.rb
  • app/models/ibkr_item/report_parser.rb
  • app/models/account/provider_import_adapter.rb
  • app/models/provider/ibkr_flex.rb
  • db/migrate/20260510140000_create_ibkr_items_and_accounts.rb
  • app/models/ibkr_account/activities_processor.rb
  • app/models/ibkr_item/syncer.rb
  • app/controllers/ibkr_items_controller.rb
  • db/schema.rb

Copy link
Copy Markdown
Collaborator

jjmata commented May 10, 2026

Great to see the IBKR integration land — the architecture follows the established provider pattern (IbkrItem / IbkrAccount mirroring SnapTrade, Coinbase, etc.) which makes it easy to follow. The decomposition into separate processors (activities_processor, holdings_processor, historical_balances_sync) is well-organised. A few observations:

IBKR uses Flex Query Reports (XML pull) rather than a push/REST API
The ibkr_flex.rb client is a polling model — request a report, poll until ready, then download XML. This is meaningfully different from the webhook-driven or REST providers. Worth making sure the sync job is resilient to the two-stage poll (e.g. if the first poll times out, does a retry re-request or re-poll?). It's also worth documenting in the user-facing setup flow that Flex token setup on the IBKR portal is required and can take time to propagate.

ibkr_items_controller.rb is 265 lines
By far the largest provider controller in the codebase (most others are under 80 lines). Some of that bulk is the multi-step setup wizard, but there may be opportunity to push the setup-state logic closer to the model (e.g. IbkrItem knowing its own setup step), consistent with the convention of skinny controllers / fat models.

report_parser.rb XML parsing: error handling for malformed reports
The Flex report parser (129 lines) walks the XML document assuming certain element/attribute names. If IBKR changes their XML schema (it's happened before) or the report comes back truncated, a NoMethodError / nil dereference could propagate as an unhandled exception. A clearly-scoped rescue that wraps the parse and surfaces a ParseError (rather than a raw backtrace in Sentry/logs) would make debugging much easier when schema drift occurs.

activities_processor.rb at 223 lines handles several different activity types
The processor dispatches on IBKR activity codes (dividends, fees, cash transfers, etc.). Consider adding a comment mapping the IBKR activity type codes handled here to the IBKR Flex documentation reference, since those codes aren't obvious to future maintainers who haven't worked with IBKR's schema.


Generated by Claude Code

@gian-reto gian-reto force-pushed the gta/ibkr-provider branch 2 times, most recently from c1286b8 to 03e3f6d Compare May 10, 2026 20:23
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/models/account/opening_balance_manager.rb`:
- Around line 54-60: The memoized `@oldest_entry_date` uses ||= which caches a
nil/old value and causes stale validation; change it to compute freshest value
on each call by removing the ||= memoization and returning the computed date
directly (i.e., always evaluate the block that checks
opening_anchor_valuation&.entry and calls
account.entries.where.not(...).minimum(:date) or
account.entries.minimum(:date)); update the method/attribute accessor that
references `@oldest_entry_date` so it no longer relies on cached state (or
explicitly clear `@oldest_entry_date` before use) to ensure set_opening_balance
validates against current data.

In `@app/models/ibkr_account/historical_balances_sync.rb`:
- Around line 27-29: The code calls row.with_indifferent_access inside the
filter_map assuming every payload row is a Hash; add a type guard to skip
malformed rows before calling with_indifferent_access (e.g., return nil/next for
nil or non-Hash rows) so the filter_map block only processes valid hashes;
update the block around filter_map/with_indifferent_access to check
row.is_a?(Hash) or respond_to?(:to_hash) and then call with_indifferent_access
and continue using currency = data[:currency].presence&.upcase.

In `@test/models/ibkr_item/syncer_test.rb`:
- Around line 6-16: The test currently constructs IbkrItem::Syncer in setup,
which can capture credentials at initialization and mask the missing-token path;
modify the test "perform_sync records a single auth error when credentials are
missing" to build the syncer after clearing credentials by moving the
instantiation of IbkrItem::Syncer (currently assigned to `@syncer` in setup) into
the test body after `@ibkr_item.update`!(token: nil) (e.g., syncer =
IbkrItem::Syncer.new(`@ibkr_item`)), then call syncer.perform_sync(sync) so
perform_sync executes with the missing-token state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d49f544d-2c38-4b74-bd30-3e3e9821bb5e

📥 Commits

Reviewing files that changed from the base of the PR and between c1286b8 and 03e3f6d.

⛔ Files ignored due to path filters (1)
  • flake.lock is excluded by !**/*.lock
📒 Files selected for processing (77)
  • app/components/UI/account/activity_date.html.erb
  • app/controllers/accounts_controller.rb
  • app/controllers/ibkr_items_controller.rb
  • app/controllers/transactions_controller.rb
  • app/javascript/controllers/time_series_chart_controller.js
  • app/models/account.rb
  • app/models/account/opening_balance_manager.rb
  • app/models/account/provider_import_adapter.rb
  • app/models/account/syncer.rb
  • app/models/balance/sync_cache.rb
  • app/models/data_enrichment.rb
  • app/models/family.rb
  • app/models/family/ibkr_connectable.rb
  • app/models/holding.rb
  • app/models/holding/materializer.rb
  • app/models/holding/portfolio_cache.rb
  • app/models/ibkr_account.rb
  • app/models/ibkr_account/activities_processor.rb
  • app/models/ibkr_account/data_helpers.rb
  • app/models/ibkr_account/historical_balances_sync.rb
  • app/models/ibkr_account/holdings_processor.rb
  • app/models/ibkr_account/processor.rb
  • app/models/ibkr_item.rb
  • app/models/ibkr_item/importer.rb
  • app/models/ibkr_item/provided.rb
  • app/models/ibkr_item/report_parser.rb
  • app/models/ibkr_item/syncer.rb
  • app/models/ibkr_item/unlinking.rb
  • app/models/provider/ibkr_adapter.rb
  • app/models/provider/ibkr_flex.rb
  • app/models/provider_connection_status.rb
  • app/models/snaptrade_account/processor.rb
  • app/models/trade.rb
  • app/models/transaction.rb
  • app/models/transaction/activity_security_preloader.rb
  • app/views/accounts/index.html.erb
  • app/views/holdings/_cash.html.erb
  • app/views/holdings/_cost_basis_cell.html.erb
  • app/views/holdings/_holding.html.erb
  • app/views/ibkr_items/_ibkr_item.html.erb
  • app/views/ibkr_items/select_existing_account.html.erb
  • app/views/ibkr_items/setup_accounts.html.erb
  • app/views/settings/providers/_ibkr_panel.html.erb
  • app/views/settings/providers/show.html.erb
  • app/views/trades/_trade.html.erb
  • app/views/transactions/_transaction.html.erb
  • config/locales/views/ibkr_items/en.yml
  • config/locales/views/settings/en.yml
  • config/routes.rb
  • db/migrate/20260510140000_create_ibkr_items_and_accounts.rb
  • db/migrate/20260510150000_add_extra_to_trades.rb
  • db/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rb
  • db/schema.rb
  • flake.nix
  • test/controllers/ibkr_items_controller_test.rb
  • test/fixtures/files/ibkr/flex_statement.xml
  • test/fixtures/ibkr_accounts.yml
  • test/fixtures/ibkr_items.yml
  • test/models/account/opening_balance_manager_test.rb
  • test/models/account/provider_import_adapter_test.rb
  • test/models/account/syncer_test.rb
  • test/models/account_ibkr_creation_test.rb
  • test/models/account_test.rb
  • test/models/balance/sync_cache_test.rb
  • test/models/holding/materializer_test.rb
  • test/models/holding/portfolio_cache_test.rb
  • test/models/holding_test.rb
  • test/models/ibkr_account/historical_balances_sync_test.rb
  • test/models/ibkr_account_processor_test.rb
  • test/models/ibkr_item/syncer_test.rb
  • test/models/ibkr_item_importer_test.rb
  • test/models/ibkr_item_report_parser_test.rb
  • test/models/ibkr_item_test.rb
  • test/models/snaptrade_account_processor_test.rb
  • test/models/trade_test.rb
  • test/models/transaction/activity_security_preloader_test.rb
  • test/models/transaction_test.rb
✅ Files skipped from review due to trivial changes (12)
  • app/views/holdings/_cash.html.erb
  • test/fixtures/files/ibkr/flex_statement.xml
  • app/javascript/controllers/time_series_chart_controller.js
  • app/components/UI/account/activity_date.html.erb
  • config/locales/views/settings/en.yml
  • test/fixtures/ibkr_items.yml
  • app/views/holdings/_holding.html.erb
  • test/models/transaction_test.rb
  • test/fixtures/ibkr_accounts.yml
  • test/models/trade_test.rb
  • config/locales/views/ibkr_items/en.yml
  • test/models/transaction/activity_security_preloader_test.rb
🚧 Files skipped from review as they are similar to previous changes (57)
  • db/migrate/20260510151000_add_raw_equity_summary_payload_to_ibkr_accounts.rb
  • app/models/data_enrichment.rb
  • app/models/family.rb
  • app/views/settings/providers/show.html.erb
  • app/models/balance/sync_cache.rb
  • app/models/provider_connection_status.rb
  • app/views/trades/_trade.html.erb
  • app/models/holding.rb
  • app/models/account/syncer.rb
  • app/controllers/accounts_controller.rb
  • app/views/ibkr_items/select_existing_account.html.erb
  • app/controllers/transactions_controller.rb
  • app/views/accounts/index.html.erb
  • test/models/holding_test.rb
  • config/routes.rb
  • test/models/account/syncer_test.rb
  • test/models/ibkr_item_importer_test.rb
  • test/models/holding/portfolio_cache_test.rb
  • test/models/account_test.rb
  • app/views/holdings/_cost_basis_cell.html.erb
  • app/models/family/ibkr_connectable.rb
  • app/models/ibkr_item/provided.rb
  • test/models/account/provider_import_adapter_test.rb
  • app/models/ibkr_item/importer.rb
  • app/models/holding/portfolio_cache.rb
  • app/views/ibkr_items/_ibkr_item.html.erb
  • app/models/provider/ibkr_adapter.rb
  • app/models/account/provider_import_adapter.rb
  • test/models/balance/sync_cache_test.rb
  • app/models/holding/materializer.rb
  • test/models/snaptrade_account_processor_test.rb
  • test/models/ibkr_item_report_parser_test.rb
  • app/models/snaptrade_account/processor.rb
  • test/models/account_ibkr_creation_test.rb
  • app/models/transaction.rb
  • app/models/trade.rb
  • app/models/ibkr_account/processor.rb
  • app/models/transaction/activity_security_preloader.rb
  • app/views/transactions/_transaction.html.erb
  • db/migrate/20260510150000_add_extra_to_trades.rb
  • db/migrate/20260510140000_create_ibkr_items_and_accounts.rb
  • app/models/ibkr_item/unlinking.rb
  • app/views/settings/providers/_ibkr_panel.html.erb
  • app/models/account.rb
  • app/models/ibkr_item/report_parser.rb
  • app/views/ibkr_items/setup_accounts.html.erb
  • flake.nix
  • test/models/holding/materializer_test.rb
  • app/models/ibkr_account.rb
  • app/models/ibkr_account/data_helpers.rb
  • test/controllers/ibkr_items_controller_test.rb
  • app/models/ibkr_item.rb
  • db/schema.rb
  • app/models/provider/ibkr_flex.rb
  • app/controllers/ibkr_items_controller.rb
  • app/models/ibkr_account/activities_processor.rb
  • app/models/ibkr_item/syncer.rb

Comment thread app/models/account/opening_balance_manager.rb Outdated
Comment thread app/models/ibkr_account/historical_balances_sync.rb
Comment thread test/models/ibkr_item/syncer_test.rb Outdated
@gian-reto
Copy link
Copy Markdown
Contributor Author

Great to see the IBKR integration land — the architecture follows the established provider pattern (IbkrItem / IbkrAccount mirroring SnapTrade, Coinbase, etc.) which makes it easy to follow. The decomposition into separate processors (activities_processor, holdings_processor, historical_balances_sync) is well-organised. A few observations:

IBKR uses Flex Query Reports (XML pull) rather than a push/REST API The ibkr_flex.rb client is a polling model — request a report, poll until ready, then download XML. This is meaningfully different from the webhook-driven or REST providers. Worth making sure the sync job is resilient to the two-stage poll (e.g. if the first poll times out, does a retry re-request or re-poll?). It's also worth documenting in the user-facing setup flow that Flex token setup on the IBKR portal is required and can take time to propagate.

ibkr_items_controller.rb is 265 lines By far the largest provider controller in the codebase (most others are under 80 lines). Some of that bulk is the multi-step setup wizard, but there may be opportunity to push the setup-state logic closer to the model (e.g. IbkrItem knowing its own setup step), consistent with the convention of skinny controllers / fat models.

report_parser.rb XML parsing: error handling for malformed reports The Flex report parser (129 lines) walks the XML document assuming certain element/attribute names. If IBKR changes their XML schema (it's happened before) or the report comes back truncated, a NoMethodError / nil dereference could propagate as an unhandled exception. A clearly-scoped rescue that wraps the parse and surfaces a ParseError (rather than a raw backtrace in Sentry/logs) would make debugging much easier when schema drift occurs.

activities_processor.rb at 223 lines handles several different activity types The processor dispatches on IBKR activity codes (dividends, fees, cash transfers, etc.). Consider adding a comment mapping the IBKR activity type codes handled here to the IBKR Flex documentation reference, since those codes aren't obvious to future maintainers who haven't worked with IBKR's schema.

Generated by Claude Code

Thanks for the review! I looked through the points and here are my thoughts:

Flex / polling model: You're right that the API of IBKR Flex Queries is quite different from the other providers, but to my knowledge, Flex Queries are the only way for individuals to access their IBKR data easily via simple HTTP calls, and without having to set up additional tools (like a separate IB Gateway, for example). Unfortunately, the API requires one call to kick off the report generation, and another one to obtain the report (and we don't know when it's ready!), which is why polling is needed. In the current flow, both SendRequest and GetStatement already use retry/backoff, and polling reuses the same reference code within a single sync attempt. Persisting and resuming reference codes across sync attempts is overkill in my opinion, so I think the current approach is a reasonable balance between robustness and complexity.

Controller size: fair note. After comparing it with the other provider controllers in Sure, it still feels within the normal range for controllers that own account setup / linking flows. So if it's fine for you, I'd keep this as-is for now as well.

XML Report parser robustness: I agree with the comment and improved the error handling clarity in an additional commit. The parser now raises a scoped IbkrItem::ReportParser::ParseError for malformed XML and missing expected Flex structure (missing root, missing statements, or missing account identifiers), so schema drift should fail much more clearly now.

Activity codes: I took another look and I don't think we're really dispatching on opaque IBKR codes so much as on the human-readable string values that appear in Flex exports (DEPOSITS/WITHDRAWALS, DIVIDENDS, BUY, SELL). I'm also not aware of a useful upstream reference that enumerates these cleanly, so I'd prefer not to add a documentation comment that implies stronger official IBKR coverage than actually exists. The documentation of the Flex Query API is quite sparse, so the only way to get a comprehensive list of activity types is to look at an actual export (which is what I used to implement this)...

TL;DR: I think the current implementation is a reasonable balance between robustness and complexity, so I only addressed the XML parsing error handling in a more explicit way. Let me know if that is sufficient from your perspective!

@gian-reto
Copy link
Copy Markdown
Contributor Author

@jjmata Uff, I saw you already merged the bank sync cleanup 😅 I'm rebasing now, and will update everything such that the provider is integrated into the new structure.

@gian-reto gian-reto force-pushed the gta/ibkr-provider branch from 52c22dd to 369d7da Compare May 10, 2026 23:11
@gian-reto
Copy link
Copy Markdown
Contributor Author

@jjmata Alright, I rebased to the latest main and integrated the IBKR provider into the new bank sync structure (looks beautiful, by the way!). I also pushed various fixes to address the remaining threads by CodeRabbit.

Lastly, I did another smoke-test to confirm that everything works as expected, and I wasn't able to find any issues. So IMO this is ready to merge, thanks for the patience!

Copy link
Copy Markdown
Collaborator

jjmata commented May 11, 2026

Solid integration — the XML parser, multi-currency trade handling via exchange_rate on the Trade model, HistoricalBalancesSync from equity summary rows, and the FX plumbing through import_trade all look well thought-out. A few things to review before merge:


1. cleanup_stale_calculated_rows_on_latest_provider_snapshot is now unconditional

The old implementation only deleted calculated holdings whose securities were not in the provider snapshot (scoped by provider_security_ids). The new implementation deletes all account_provider_id: nil rows on the snapshot date:

deleted_count = account.holdings
  .where(account_provider_id: nil, date: provider_snapshot_date)
  .delete_all

For a pure-IBKR account this is fine. But for an account where a user has also entered manual holdings on the same date as a provider snapshot, those manual rows (also account_provider_id: nil) will be silently deleted on every sync. The previous scoping by provider_security_ids preserved manual holdings for securities the provider doesn't track. Worth confirming this regression is intentional.


2. IBKR cash transaction FX rates stored in extra, not on the model

Trades get a first-class exchange_rate column, which Balance::SyncCache now picks up via e.entryable.respond_to?(:exchange_rate). IBKR cash transactions (deposits, withdrawals, dividends) store their FX rate in extra["exchange_rate"]Transaction has no exchange_rate attribute, so those flows fall back to the standard Money rate lookup during balance calculations. For IBKR accounts denominated in a non-base currency, dividend and deposit amounts in balance history may diverge slightly from what IBKR reports. Not necessarily a blocker given that HistoricalBalancesSync overrides the daily balance totals anyway, but worth noting as a follow-up.


3. destroy proceeds even when unlink_all! raises

begin
  @ibkr_item.unlink_all!(dry_run: false)
rescue => e
  Rails.logger.warn("IBKR unlink during destroy failed: ...")
end
@ibkr_item.destroy_later

If unlink_all! fails partway through, orphaned AccountProvider rows could remain. destroy_later via DestroyJob would then run against an item with partially-unlinked providers. Worth verifying that DestroyJob handles this gracefully (or that unlink_all! is idempotent enough to be called again inside the job).


4. Nit: redundant to_i on @fee_transactions_count

@fee_transactions_count = 0 is assigned at the top of process, so @fee_transactions_count.to_i + 1 in import_commission_transaction is a no-op guard. Minor, just noisy.


Generated by Claude Code

@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented May 11, 2026

@jjmata Uff, I saw you already merged the bank sync cleanup 😅 I'm rebasing now, and will update everything such that the provider is integrated into the new structure.

Yeah, sorry ... things move at a different pace these days. Are you in the Discord? Tried to give a heads up in some PRs (BREX) but forgot about this one I guess. 🙄

This month is shaping up to be "sync improvements" release month!

@gian-reto gian-reto force-pushed the gta/ibkr-provider branch from 369d7da to 8b41831 Compare May 11, 2026 16:59
@gian-reto
Copy link
Copy Markdown
Contributor Author

Solid integration — the XML parser, multi-currency trade handling via exchange_rate on the Trade model, HistoricalBalancesSync from equity summary rows, and the FX plumbing through import_trade all look well thought-out. A few things to review before merge:

1. cleanup_stale_calculated_rows_on_latest_provider_snapshot is now unconditional

The old implementation only deleted calculated holdings whose securities were not in the provider snapshot (scoped by provider_security_ids). The new implementation deletes all account_provider_id: nil rows on the snapshot date:

deleted_count = account.holdings
  .where(account_provider_id: nil, date: provider_snapshot_date)
  .delete_all

For a pure-IBKR account this is fine. But for an account where a user has also entered manual holdings on the same date as a provider snapshot, those manual rows (also account_provider_id: nil) will be silently deleted on every sync. The previous scoping by provider_security_ids preserved manual holdings for securities the provider doesn't track. Worth confirming this regression is intentional.

2. IBKR cash transaction FX rates stored in extra, not on the model

Trades get a first-class exchange_rate column, which Balance::SyncCache now picks up via e.entryable.respond_to?(:exchange_rate). IBKR cash transactions (deposits, withdrawals, dividends) store their FX rate in extra["exchange_rate"]Transaction has no exchange_rate attribute, so those flows fall back to the standard Money rate lookup during balance calculations. For IBKR accounts denominated in a non-base currency, dividend and deposit amounts in balance history may diverge slightly from what IBKR reports. Not necessarily a blocker given that HistoricalBalancesSync overrides the daily balance totals anyway, but worth noting as a follow-up.

3. destroy proceeds even when unlink_all! raises

begin
  @ibkr_item.unlink_all!(dry_run: false)
rescue => e
  Rails.logger.warn("IBKR unlink during destroy failed: ...")
end
@ibkr_item.destroy_later

If unlink_all! fails partway through, orphaned AccountProvider rows could remain. destroy_later via DestroyJob would then run against an item with partially-unlinked providers. Worth verifying that DestroyJob handles this gracefully (or that unlink_all! is idempotent enough to be called again inside the job).

4. Nit: redundant to_i on @fee_transactions_count

@fee_transactions_count = 0 is assigned at the top of process, so @fee_transactions_count.to_i + 1 in import_commission_transaction is a no-op guard. Minor, just noisy.

Generated by Claude Code

Thanks for the review! I went through each point in detail and addressed the first and last ones. The fixes are already fixupped and pushed.

  1. cleanup_stale_calculated_rows_on_latest_provider_snapshot being unconditional: Good catch, this was a regression. I narrowed the cleanup again so that on the latest provider snapshot date it only removes non-provider rows for securities that are actually present in the provider snapshot, which preserves unrelated manual holdings on the same day. I also added a regression test for that case.
  2. IBKR cash transaction FX rates stored in extra rather than on the model: I don’t think this is applicable anymore in the current branch. Transaction now exposes an exchange_rate accessor backed by extra, and Balance::SyncCache uses entryable.exchange_rate for both Trade and Transaction, so IBKR cash flows already use the provider-supplied historical FX rate during conversion.
  3. destroy proceeding even when unlink_all! fails: I don’t think this is a correctness issue as implemented. unlink_all! is already best-effort and rescues per provider account internally, and any remaining AccountProvider rows are still cleaned up by the normal dependent-destroy path when the IbkrItem destroys its ibkr_accounts. Holdings are also nullified via the AccountProvider association.
  4. Redundant to_i on @fee_transactions_count: Agreed. That was just noise, and I cleaned it up as well.

@gian-reto
Copy link
Copy Markdown
Contributor Author

@jjmata Uff, I saw you already merged the bank sync cleanup 😅 I'm rebasing now, and will update everything such that the provider is integrated into the new structure.

Yeah, sorry ... things move at a different pace these days. Are you in the Discord? Tried to give a heads up in some PRs (BREX) but forgot about this one I guess. 🙄

This month is shaping up to be "sync improvements" release month!

No worries, it was actually more straightforward than I expected! I somehow didn't see that there is a Discord, but I joined today.

@gian-reto
Copy link
Copy Markdown
Contributor Author

@jjmata I addressed your points above and pushed the changes, so this should be ready!

Copy link
Copy Markdown
Collaborator

@jjmata jjmata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to stop reviewing, as I see a bunch of non-IBKR related changes? Did you merge another branch/PR in to your tree for any reason?

Comment thread app/controllers/accounts_controller.rb
Comment thread app/controllers/accounts_controller.rb
Comment thread app/controllers/transactions_controller.rb
Comment thread app/models/account/opening_balance_manager.rb
@gian-reto
Copy link
Copy Markdown
Contributor Author

I'm going to stop reviewing, as I see a bunch of non-IBKR related changes? Did you merge another branch/PR in to your tree for any reason?

Sorry for the confusion, I should have carried over more of the context from my original PR here as well. No, I did not merge another branch/PR into this one.

In addition to the core IBKR provider work, I also made a few follow-up changes that came up during smoke-testing and review. In particular, I wanted investment activity in the history views to display the related security logo instead of only a generic colored circle/letter, and I fixed a couple of UI issues I noticed there at the same time.

Most of the controller/model changes you pointed out below are related to that follow-up commit ("Add logos in activity history"). In the original PR, CodeRabbit also flagged that this introduced an N+1 lookup path for resolving the related security on transaction rows, see: #1712 (comment)

That is why I added Transaction::ActivitySecurityPreloader and the corresponding preload calls in the activity/transactions pages.

The OpeningBalanceManager change is separate from the logo/UI follow-up: that one came from the IBKR import flow itself, because the processor repairs the default opening anchor after importing activity/history, and I needed to make that logic ignore the anchor entry itself when validating the oldest entry date.

@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented May 12, 2026

OK, that helps explain things! So much activity that I missed the comments from the old PR.

Can you resolve the merge conflicts and remaining PR review comments here?

@gian-reto
Copy link
Copy Markdown
Contributor Author

OK, that helps explain things! So much activity that I missed the comments from the old PR.

Can you resolve the merge conflicts and remaining PR review comments here?

No worries! I will rebase this evening if that's fine for you, as I don't have enough time right now. Is there anything else you would like me to change?

One thing I mentioned in my original PR was that I added a Nix devshell, so I can more easily work in this repo on NixOS (as an alternative to the devcontainer). However, this commit ("Add nix devshell") is not related to the IBKR provider and the other improvements, so if you want I can remove it if you don't want to include a flake.nix in this repo.

@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented May 12, 2026

No rush! I'm trying to work through the backlog or PRs, so the cleanest (and smallest) ones are the easier ones to merge.

One thing I mentioned in my original PR was that I added a Nix devshell, so I can more easily work in this repo on NixOS (as an alternative to the devcontainer). However, this commit ("Add nix devshell") is not related to the IBKR provider and the other improvements, so if you want I can remove it if you don't want to include a flake.nix in this repo.

No, that's great ... but please separate it so others can see it / comment on it and it gets release notes treatment! Otherwise it goes unnoticed. :-)

@jjmata jjmata added this to the v0.7.1 milestone May 12, 2026
@gian-reto gian-reto force-pushed the gta/ibkr-provider branch from 8b41831 to c78cf79 Compare May 12, 2026 19:25
@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented May 12, 2026

Thanks for the quick progress here. I see at least 2 or 3 other thread that need to go into separate PRs:

  • The privacy mode additions
  • The exchange rate / sync / holding fixes
  • The Nix stuff

Anything else, @gian-reto?

@gian-reto
Copy link
Copy Markdown
Contributor Author

Thanks for the quick progress here. I see at least 2 or 3 other thread that need to go into separate PRs:

  • The privacy mode additions
  • The exchange rate / sync / holding fixes
  • The Nix stuff

Anything else, @gian-reto?

Working on it! The exchange rate holding fixes kind of need to be in this PR, as the IBKR provider would be broken without it... I'm currently removing the Nix stuff. As for the privacy mode additions, they're technically not necessary for this PR, but it's a super small change limited to the daily totals only, so I'd also prefer to keep it here. I'll probably do some more privacy-related changes, but I'll put them in a separate PR.

Would you be ok with it if I remove only the Nix stuff and keep the other changes in this PR?

@gian-reto gian-reto force-pushed the gta/ibkr-provider branch from c78cf79 to 805a02c Compare May 12, 2026 20:15
@gian-reto
Copy link
Copy Markdown
Contributor Author

@jjmata alright, I rebased again, dropped the Nix stuff and pushed again. If you agree that we should keep the exchange rate and the few privacy mode changes in (I would strongly suggest it, especially the exchange rate changes), this should be ready.

@gian-reto gian-reto force-pushed the gta/ibkr-provider branch from 805a02c to 762f6b7 Compare May 12, 2026 20:30
@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented May 12, 2026

Sure, you can leave those here, but can we find a better column name than extra in the Trade model? Not very descriptive. What is expected to be logged there?

Also, what are the SnapTrade changes related to?

@gian-reto
Copy link
Copy Markdown
Contributor Author

Sure, you can leave those here, but can we find a better column name than extra in the Trade model? Not very descriptive. What is expected to be logged there?

Also, what are the SnapTrade changes related to?

I mainly did it because I saw there was already transaction.extra and others, so I wanted to keep it in line with existing code, but I can also put it directly on the model if you prefer.

Regardig SnapTrade: Only SnapTrade (in addition to now IBKR of course) needed the change because its processor was manually calculating total_balance as holdings value + cash, which breaks for multi-currency portfolios unless you also do FX conversion. Most of the other providers already trust the provider-reported total balance directly (or already have separate logic for this), so the issue wasn’t present there in the same way.

@jjmata
Copy link
Copy Markdown
Collaborator

jjmata commented May 12, 2026

I mainly did it because I saw there was already transaction.extra and others, so I wanted to keep it in line with existing code, but I can also put it directly on the model if you prefer.

Are both used for similar metadata?

EDIT: nevermind, I see a lot of use of extra fields. 🙄

@jjmata jjmata merged commit ce5d7dd into we-promise:main May 12, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

contributor:verified Contributor passed trust analysis. pr:verified PR passed security analysis.

Development

Successfully merging this pull request may close these issues.

2 participants